<template>
  <Card class="full-height full-width topology-card">
    <template #title>
      业务请求调用链路：
      <Select
        v-model:value="selectedRequsetValue"
        style="width: 200px"
        :options="selectOptions"
        @change="changeRequest"
      ></Select>
      <Button
        type="primary"
        size="small"
        :href="selectedRequset.url"
        style="margin-left: 10px"
        target="_blank"
        >业务入口</Button
      >
    </template>

    <div
      id="d3-container"
      class="full-height full-width"
      style="flex-grow: 1; overflow: hidden"
    >
      <!-- D3 will append the SVG to this div -->
    </div>
  </Card>
</template>
  
  <script lang="ts">
import * as d3 from "d3";
import injectedNodeSvg from "../../../assets/topology-node/injectedNode.svg";
import injectingNodeSvg from "../../../assets/topology-node/injectingNode.svg";
import runningNodeSvg from "../../../assets/topology-node/runningNode.svg";
import {
  selectOptions,
  getData,
  remainingData,
  remainingTreeData,
  cheapestTreeData,
} from "./dataJson.ts";
import { Card, Select, Button } from "ant-design-vue";
import { onBeforeMount, ref } from "vue";
import _ from "lodash";
import { InstanceItem } from "@/type/instances";
import eventBus from "../../../utils/eventBus";
import { loginApi } from "@/api/topology";
import { getChaosStatusApi } from "@/api/chaos";
import { useInstanceModuleStore } from "@/store/instance"; //
export default {
  name: "ForceDirectedGraph",
  components: {
    Select,
    Card,
    Button,
  },
  data() {
    const selectedRequsetValue = ref(selectOptions[0].value);
    const requestData = ref<InstanceItem[]>([]);
    const nodes = ref<any[]>([]);
    const links = ref<any[]>([]);
    const nodeWidth = 75;
    const nodeHeight = 75;
    const injectedChaos = ref<any>({});
    const selectedRequset = ref<any>(selectOptions[0]);

    const pollingInterval = ref<any>(null);

    requestData.value = getData(selectedRequsetValue.value);

    const transformData = (originalData) => {
      const nodesMap = new Map();
      const nodeLinks = [];

      // 将每个节点的状态添加到nodesMap中
      for (const node of originalData) {
        nodesMap.set(node.name, node.status);
      }

      // 遍历每个节点以生成links
      for (const node of originalData) {
        for (const nextNode of node.next) {
          if (nodesMap.has(nextNode)) {
            nodeLinks.push({
              source: node.name,
              target: nextNode,
              // 检查任何节点是否为error
              status:
                nodesMap.get(node.name) === "error" ||
                nodesMap.get(nextNode) === "error"
                  ? "error"
                  : "running",
            });
          }
        }
      }
      nodes.value = Array.from(nodesMap).map(([name, status]) => ({
        name,
        status,
      }));
      links.value = nodeLinks;
    };
    onBeforeMount(() => {
      transformData(requestData.value);
    });

    return {
      selectedRequsetValue,
      nodeWidth,
      nodeHeight,
      requestData,
      links,
      nodes,
      remainingData,
      getData,
      selectOptions,
      transformData,
      pollingInterval,
      injectedChaos,
      selectedRequset,
    };
  },
  mounted() {
    this.init();
  },
  methods: {
    init() {
      // 数据
      // const data = _.cloneDeep(remainingTreeData);
      const data = _.cloneDeep(cheapestTreeData);

      const maxNodes = this.getMaxNodesPerLevel(data);
      const baseSize = 200; // 假设当最大子节点数为1时的基础大小
      const nodeSize = baseSize / Math.sqrt(maxNodes); // 调整大小

      // 设置
      const container = d3.select("#d3-container");
      const width = +container.node().getBoundingClientRect().width,
        height = +container.node().getBoundingClientRect().height;

      const svg = container
        .append("svg")
        .attr("width", width)
        .attr("height", height);
      // 创建一个< g >元素，并应用偏移
      const g = svg.append("g").attr("transform", "translate(50, 0)"); 
      // 箭头定义

      svg
        .append("defs")
        .selectAll("marker")
        .data(["end"])
        .enter()
        .append("marker")
        .attr("id", String)
        .attr("viewBox", "0 -5 10 10") // 设置marker的视图框，确定它的坐标和大小。
        .attr("refX", 6)
        .attr("refY", 0)
        // 确定箭头的大小。
        .attr("markerWidth", 5)
        .attr("markerHeight", 5)
        // 这使得箭头与线对齐，无论线的方向如何。
        .attr("orient", "auto")
        // 这里我们真正定义箭头的形状。它是一个简单的三角形。
        .append("path")
        .attr("d", "M0,-4L8,0L0,4L2,0")
        .attr("fill", "#000000");

      const treeLayout = d3.tree().size([height - 150,width - 50]);
      const root = d3.hierarchy(data);
      root.y = 0;
      root.x = width / 2 + 100;
      treeLayout(root);
      const marker = (color: string, markerId: string) => {
        svg
          .append("defs") //使用<defs>标签来定义可重复使用的对象。
          .append("marker") // 添加一个SVG的marker元素，表示箭头
          .attr("id", markerId)
          .attr("viewBox", "0 -5 10 10") // 设置marker的视图框，确定它的坐标和大小。
          .attr("refX", 6)
          .attr("refY", 0)
          // 确定箭头的大小。
          .attr("markerWidth", 5)
          .attr("markerHeight", 5)
          // 这使得箭头与线对齐，无论线的方向如何。
          .attr("orient", "auto")
          // 这里我们真正定义箭头的形状。它是一个简单的三角形。
          .append("path")
          .attr("d", "M0,-4L8,0L0,4L2,0")
          .attr("fill", color);

        return "url(#" + markerId + ")";
      };
      const vm = this;
      // 连线
      g.selectAll(".link")
        .data(root.links())
        .enter()
        .append("line")
        // .filter((d) => d.target.children) // 只绑定具有子节点的连接
        .attr("class", "link")
        .attr("stroke-width", 2.5)
        .attr("x1", (d) => this.getSourceXY(d)[1])
.attr("y1", (d) => this.getSourceXY(d)[0])
.attr("x2", (d) => this.getTargetXY(d)[1])
.attr("y2", (d) => this.getTargetXY(d)[0])
        .attr("stroke", (d) => {
          return this.getColorBystatus(d.source.data.status);
        })
        .each(function (d: any) {
          const link = d3.select(this);
          const color = vm.getColorBystatus(d.source.data.status);
          const markerId =
            "marker-" + d.source.data.name + "-" + d.target.data.name;
          link.attr("marker-end", marker(color, markerId));
        });
      // .attr("marker-end", "url(#end)");
      // 节点
      const nodes = g
        .selectAll(".node")
        .data(root.descendants())
        .enter()
        .append("g")
        .attr("class", "node")
        .attr("transform", (d) => `translate(${d.y},${d.x})`)
        .append("image")
        .attr("xlink:href", (d) => {
          return this.getNodeBystatus(d.data.status);
        })
    //     .attr("x", d => d.x - nodeSize / 2)
    // .attr("y", d => d.y - nodeSize / 2)
    .attr("width", nodeSize)
    .attr("height", nodeSize)
        .attr(
          "transform",
          () => `translate(-${nodeSize / 2},-${nodeSize / 2})`
        );

      const text = g
        .selectAll(".node")
        .append("text")
        .attr("dy", "3.5em") // 垂直偏移以使文本出现在节点下方
        .attr("text-anchor", "middle") // 文本锚点设置为中间
        .text(function (d) {
          return d.data.name; // 根据数据设置文本内容
        });
    },

    getMaxNodesPerLevel(node: any) {
      let levels = {};

      function recurse(node: any, level: number) {
        if (!node) return;

        levels[level] = (levels[level] || 0) + 1;

        if (node.children) {
          node.children.forEach((child) => recurse(child, level + 1));
        }
      }

      recurse(node, 0);

      return Math.max(...Object.values(levels));
    },
    getNodeBystatus(status: string) {
      switch (status) {
        case "injectFailed":
          return runningNodeSvg;
          break;
        case "running":
          return runningNodeSvg;
          break;
        case "injecting":
          return injectingNodeSvg;
          break;
        case "injected":
          return injectedNodeSvg;
          break;
      }
    },
    getColorBystatus(status: string) {
      switch (status) {
        case "running":
          return "#8bd496";
          break;
        case "injectFailed":
          return "#8bd496";
          break;
        case "injecting":
          return "#f7c01a";
          break;
        case "injected":
          return "#EF233C";
          break;
      }
    },
    getSourceXY(d: any) {
      if (!d?.source || !d?.target) {
        return [0, 0]; // 返回默认坐标，或者其他适当的值
      }
      const deltaX = d.target.x - d.source.x;
      const deltaY = d.target.y - d.source.y;
      const dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
      const normX = deltaX / dist;
      const normY = deltaY / dist;
      const sourceX = d.source.x + (this.nodeWidth / 2) * normX;
      const sourceY = d.source.y + (this.nodeHeight / 2) * normY;
      return [sourceX, sourceY];
    },
    getTargetXY(d: any) {
      if (!d?.source || !d?.target) {
        return [0, 0]; // 返回默认坐标，或者其他适当的值
      }
      const deltaX = d.target.x - d.source.x;
      const deltaY = d.target.y - d.source.y;
      const dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
      const normX = deltaX / dist;
      const normY = deltaY / dist;
      const targetX = d.target.x - (this.nodeWidth / 2) * normX;
      const targetY = d.target.y - (this.nodeHeight / 2) * normY;
      return [targetX, targetY];
    },
  },
};
</script>
  
  <style lang="less" scoped>
.select-div {
  padding-left: 20px;
}
.topology-card :deep(.ant-card-body) {
  height: 100%;
}
</style>
  